package com.adobe.ac.maven.ncss;

/*
 * Copyright 2001-2005 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;

import org.apache.maven.project.MavenProject;
import org.apache.maven.reporting.AbstractMavenReport;
import org.apache.maven.reporting.MavenReportException;
import org.codehaus.doxia.site.renderer.SiteRenderer;
import org.codehaus.plexus.util.DirectoryScanner;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.io.SAXReader;

/**
 * Goal which touches a timestamp file.
 *
 * @goal report
 */
public class NcssReportMojo
      extends AbstractMavenReport
{
   private static final String OUTPUT_NAME = "javancss";

   /**
    * Specifies the directory where the HTML report will be generated.
    * 
    * @parameter expression="${project.reporting.outputDirectory}"
    * @required
    * @readonly
    */
   private File outputDirectory;

   /**
    * Specifies the directory where the XML report will be generated.
    * 
    * @parameter default-value="${project.build.directory}"
    * @required
    */
   private File xmlOutputDirectory;

   /**
    * Specifies the location of the source files to be used.
    * 
    * @parameter expression="${project.build.sourceDirectory}"
    * @required
    * @readonly
    */
   private File sourceDirectory;

   /**
    * Specifies the maximum number of lines to take into account into the reports.
    * 
    * @parameter default-value="30"
    * @required
    */
   private int lineThreshold;

   /**
    * Specified the name of the temporary file generated by Javancss prior report generation.
    * 
    * @parameter default-value="javancss-raw-report.xml"
    * @required
    */
   private String tempFileName;

   /**
    * @parameter expression="${project}"
    * @required
    * @readonly
    */
   private MavenProject project;

   /**
    * @parameter expression="${component.org.codehaus.doxia.site.renderer.SiteRenderer}"
    * @required
    * @readonly
    */
   private SiteRenderer siteRenderer;

   /**
    * The projects in the reactor for aggregation report.
    * 
    * @parameter expression="${reactorProjects}"
    * @readonly
    */
   private List< MavenProject > reactorProjects;

   /**
    * List of ant-style patterns used to specify the java sources that should be included when running JavaNCSS.
    * If this is not specified, all .java files in the project source directories are included.
    * 
    * @parameter
    */
   private String[] includes;

   /**
    * List of ant-style patterns used to specify the java sources that should be excluded when running JavaNCSS.
    * If this is not specified, no files in the project source directories are excluded.
    * 
    * @parameter
    */
   private String[] excludes;

   /**
    * Encoding to use when reading the javancss xml file. This should match the encoding used in the source files that javancss is parsing.
    * It defaults to the <em>System.getProperty("file.encoding");</em>, but you can override it.
    * 
    * @parameter
    */
   private String forceEncoding;

   /**
    * @see org.apache.maven.reporting.MavenReport#execute(java.util.Locale)
    */
   public void executeReport(
         final Locale locale ) throws MavenReportException
   {
      if ( getLog().isDebugEnabled() )
      {
         getLog().debug( "NcssReportMojo.executeReport()" );
      }

      if ( !canGenerateReport() )
      {
         throw new MavenReportException( "Cannot generate report " );
      }

      if ( canGenerateSingleReport() )
      {
         generateSingleReport( locale );
      }
      if ( canGenerateAggregateReport() )
      {
         generateAggregateReport( locale );
      }
   }

   private void generateAggregateReport(
         final Locale locale ) throws MavenReportException
   {
      // All this work just to get "target" so that we can scan the filesystem for
      // child javancss xml files...
      final String basedir = project.getBasedir().toString();
      final String output = xmlOutputDirectory.toString();
      if ( getLog().isDebugEnabled() )
      {
         getLog().debug( "basedir: " + basedir );
         getLog().debug( "output: " + output );
      }
      String relative = null;
      if ( output.startsWith( basedir ) )
      {
         relative = output.substring( basedir.length() + 1 );
      }
      else
      {
         getLog().error( "Unable to aggregate report because I can't " + "determine the relative location of the XML report" );
         return;
      }
      getLog().debug( "relative: " + relative );
      final List< ModuleReport > reports = new ArrayList< ModuleReport >();
      for ( final Iterator< MavenProject > it = reactorProjects.iterator(); it.hasNext(); )
      {
         final MavenProject child = ( MavenProject ) it.next();
         final File xmlReport = new File( child.getBasedir() + File.separator + relative, tempFileName );
         if ( xmlReport.exists() )
         {
            reports.add( new ModuleReport( child, loadDocument( xmlReport ) ) );
         }
         else
         {
            getLog().debug( "xml file not found: " + xmlReport );
         }
      }
      getLog().debug( "Aggregating " + reports.size() + " JavaNCSS reports" );

      // parse the freshly generated file and write the report
      final NcssAggregateReportGenerator reportGenerator = new NcssAggregateReportGenerator( getSink(), getBundle( locale ), getLog() );
      reportGenerator.doReport( locale, reports, lineThreshold );
   }

   private boolean isIncludeExcludeUsed()
   {
      return ( ( excludes != null ) || ( includes != null ) );
   }

   private void generateSingleReport(
         final Locale locale ) throws MavenReportException
   {
      if ( getLog().isDebugEnabled() )
      {
         getLog().debug( "Calling NCSSExecuter with src    : " + sourceDirectory );
         getLog().debug( "Calling NCSSExecuter with output : " + buildOutputFile() );
         getLog().debug( "Calling NCSSExecuter with includes : " + includes );
         getLog().debug( "Calling NCSSExecuter with excludes : " + excludes );
      }
      // run javaNCss and produce an temp xml file
      NcssExecuter ncssExecuter;
      if ( isIncludeExcludeUsed() )
      {
         ncssExecuter = new NcssExecuter( scanForSources(), buildOutputFile() );
      }
      else
      {
         ncssExecuter = new NcssExecuter( sourceDirectory, buildOutputFile() );
      }

      ncssExecuter.execute();
      if ( !isTempReportGenerated() )
      {
         throw new MavenReportException( "Can't process temp ncss xml file." );
      }
      // parse the freshly generated file and write the report
      final NcssReportGenerator reportGenerator = new NcssReportGenerator( getSink(), getBundle( locale ), getLog() );
      reportGenerator.doReport( loadDocument(), lineThreshold );
   }

   /**
    * Load the xml file generated by javancss.
    * It first tries to load it as is.
    * If this fails it tries to load it with the forceEncoding parameter which defaults to the system property "file.encoding".
    * If this latter fails, it throws a MavenReportException.
    */
   private Document loadDocument(
         final File file ) throws MavenReportException
   {
      try
      {
         return loadDocument( file, null );
      }
      catch ( DocumentException ignored )
      {
         if ( forceEncoding == null )
         {
            forceEncoding = System.getProperty( "file.encoding" );
         }
         getLog().debug( "Loading document without specifying encoding failed, trying with forceEncoding = [" + forceEncoding + "]" );
         try
         {
            return loadDocument( file, forceEncoding );
         }
         catch ( DocumentException de )
         {

            throw new MavenReportException( de.getMessage(), de );
         }
      }
   }

   private Document loadDocument(
         final File file, final String encoding ) throws DocumentException
   {
      final SAXReader saxReader = new SAXReader();
      if ( encoding != null )
      {
         saxReader.setEncoding( encoding );
         getLog().debug( "Loading xml file with encoding : " + encoding );
      }
      return saxReader.read( file );
   }

   private Document loadDocument() throws MavenReportException
   {
      return loadDocument( buildOutputFile() );
   }

   /**
    * Check that the expected temporary file generated by JavaNCSS exists.
    * 
    * @return <code>true</code> if the temporary report exists, <code>false</code> otherwise.
    */
   private boolean isTempReportGenerated()
   {
      return buildOutputFile().exists();
   }

   /**
    * @see org.apache.maven.reporting.MavenReport#canGenerateReport()
    */
   public boolean canGenerateReport()
   {
      return ( canGenerateSingleReport() || canGenerateAggregateReport() );
   }

   private boolean canGenerateAggregateReport()
   {
      boolean result = false;
      
      if ( project.getModules().size() == 0 )
      {
         // no child modules
         result = false;
      }
      else if ( sourceDirectory != null && sourceDirectory.exists() )
      {
         // only non-source projects can aggregate
         final Collection< File > sources = scanForSources();
         result = sources == null || sources.isEmpty();
      }
      else
      {
         result = true;
      }
      
      return result;
   }

   private boolean canGenerateSingleReport()
   {
      boolean result = false;
      
      if ( sourceDirectory == null || !sourceDirectory.exists() )
      {
         result = false;
      }
      else
      {
         // now that we know we have a valid existing source directory
         // we check if any *.as files are existing.
         final Collection< File > sources = scanForSources();
         result = ( sources != null ) && ( !sources.isEmpty() );
      }
      
      return result;
   }

   /**
    * gets a list of all files in the source directory.
    * 
    * @return the list of all files in the source directory;
    */
   private Collection< File > scanForSources()
   {
      final String[] defaultIncludes =
      { "**\\*.as" };
      final DirectoryScanner scanner = new DirectoryScanner();
      if ( includes == null )
      {
         scanner.setIncludes( defaultIncludes );
      }
      else
      {
         scanner.setIncludes( includes );
      }
      if ( excludes != null )
      {
         scanner.setExcludes( excludes );
      }
      scanner.setBasedir( sourceDirectory );
      getLog().debug( "Scanning base directory " + sourceDirectory );
      scanner.scan();
      final int maxFiles = scanner.getIncludedFiles().length;
      final Collection< File > result = new ArrayList< File >( maxFiles );
      for ( int i = 0; i < maxFiles; i++ )
      {
         result.add( new File( sourceDirectory + File.separator + scanner.getIncludedFiles()[ i ] ) );
      }
      return result;
   }

   /**
    * Build a path for the output filename.
    * 
    * @return A String representation of the output filename.
    */
   /* package */File buildOutputFile()
   {
      return new File( getXmlOutputDirectory() + File.separator + tempFileName );
   }

   /**
    * @see org.apache.maven.reporting.MavenReport#getName(java.util.Locale)
    */
   public String getName(
         final Locale locale )
   {
      return getBundle( locale ).getString( "report.ncss.name" );
   }

   /**
    * @see org.apache.maven.reporting.MavenReport#getDescription(java.util.Locale)
    */
   public String getDescription(
         final Locale locale )
   {
      return getBundle( locale ).getString( "report.ncss.description" );
   }

   /**
    * @see org.apache.maven.reporting.AbstractMavenReport#getOutputDirectory()
    */
   protected String getOutputDirectory()
   {
      return outputDirectory.getAbsolutePath();
   }

   protected String getXmlOutputDirectory()
   {
      return xmlOutputDirectory.getAbsolutePath();
   }

   /**
    * @see org.apache.maven.reporting.AbstractMavenReport#getProject()
    */
   protected MavenProject getProject()
   {
      return project;
   }

   /**
    * @see org.apache.maven.reporting.AbstractMavenReport#getSiteRenderer()
    */
   protected SiteRenderer getSiteRenderer()
   {
      return siteRenderer;
   }

   /**
    * @see org.apache.maven.reporting.MavenReport#getOutputName()
    */
   public String getOutputName()
   {
      return OUTPUT_NAME;
   }

   /**
    * Getter for the source directory
    * 
    * @return the source directory as a File object.
    */
   protected File getSourceDirectory()
   {
      return sourceDirectory;
   }

   // helper to retrieve the right bundle
   private static ResourceBundle getBundle(
         final Locale locale )
   {
      return ResourceBundle.getBundle( "as3ncss", locale, NcssReportMojo.class.getClassLoader() );
   }
}
